import 'dart:async';

import 'package:flutter/material.dart';

import 'item_widget.dart';

List<Text> contentList = [];

class Test {
  /// Test definition.
  Test(this.name, this.fn, {bool? solo, bool? skip})
      : solo = solo == true,
        skip = skip == true;

  /// Only run this test.
  final bool solo;

  /// Skip this test.
  final bool skip;

  /// Test name.
  String name;

  /// Test body.
  FutureOr Function() fn;
}

/// Item states.
enum ItemState {
  /// test not run yet.
  none,

  /// test is running.
  running,

  /// test succeeded.
  success,

  /// test fails.
  failure
}

/// Menu item.
class Item {
  /// Menu item.
  Item(this.name);

  /// Menu item state.
  ItemState state = ItemState.running;

  /// Menu item name/
  String name;
}

class TestLength {
  TestLength(this.oldLength, this.newLength);

  int oldLength;
  int newLength;
}

class GroupRange {
  GroupRange(this.groupName, this.startIndex, this.endIndex);

  String groupName;
  int startIndex;
  int endIndex;
}

/// 基础测试页面
class TestPage extends StatefulWidget {
  /// Base test page.
  TestPage(this.title, {Key? key}) : super(key: key);

  /// The title.
  final String title;

  /// Test list.
  final List<Test> tests = [];

  /// 保存group的范围信息
  final Map<String, TestLength> groupTitle = {};

  /// define a test.
  void test(String name, FutureOr Function() fn) {
    tests.add(Test(name, fn));
  }

  /// define a group test.
  void group(String name, FutureOr Function() fn) {
    int oldLength = tests.length;
    fn();

    int newLength = tests.length - 1;
    groupTitle.addAll({name: TestLength(oldLength, newLength)});
  }

  /// Thrown an exception
  void fail([String? message]) {
    throw Exception(message ?? 'should fail');
  }

  @override
  TestPageState createState() => TestPageState();
}

/// Group.
mixin Group {
  /// List of tests.
  List<Test> get tests {
    // TODO: implement tests
    throw UnimplementedError();
  }

  bool? _hasSolo;
  final _tests = <Test>[];

  /// Add a test.
  void add(Test test) {
    if (!test.skip) {
      if (test.solo) {
        if (_hasSolo != true) {
          _hasSolo = true;
          _tests.clear();
        }
        _tests.add(test);
      } else if (_hasSolo != true) {
        _tests.add(test);
      }
    }
  }

  /// true if it has solo or contains item with solo feature
  bool? get hasSolo => _hasSolo;
}

class TestPageState extends State<TestPage> with Group {
  List<Item> items = [];

  Future _run() async {
    if (!mounted) {
      return null;
    }

    setState(() {
      items.clear();
    });
    _tests.clear();
    for (var test in widget.tests) {
      add(test);
    }
    for (var test in _tests) {
      var item = Item(test.name);
      contentList.add(Text(test.name,
          style: const TextStyle(fontSize: 18, color: Colors.green)));

      late int position;
      setState(() {
        position = items.length;
        items.add(item);
      });
      try {
        await test.fn();
        item = Item(test.name)..state = ItemState.success;
        print('ohFlutter: ${test.name}, result: success');
      } catch (e, st) {
        contentList.add(Text('$e, $st',
            style: const TextStyle(fontSize: 18, color: Colors.red)));
        print('ohFlutter: ${test.name}-error: $e, $st}');
        item = Item(test.name)..state = ItemState.failure;
      }

      if (!mounted) {
        return null;
      }

      setState(() {
        items[position] = item;
      });
    }
  }

  Future _runTest(int index) async {
    if (!mounted) {
      return null;
    }

    final test = _tests[index];

    var item = items[index];
    setState(() {
      contentList = [];
      item.state = ItemState.running;
    });
    contentList.add(Text(test.name,
        style: const TextStyle(fontSize: 18, color: Colors.green)));
    try {
      await test.fn();

      item = Item(test.name)..state = ItemState.success;
      print('ohFlutter: ${test.name}, result: success');
    } catch (e, st) {
      contentList.add(Text('$e, $st',
          style: const TextStyle(fontSize: 18, color: Colors.red)));
      print('ohFlutter: ${test.name}-error: $e, $st}');
      try {
        print(st);
      } catch (_) {}
      item = Item(test.name)..state = ItemState.failure;
    }

    if (!mounted) {
      return null;
    }

    setState(() {
      items[index] = item;
    });
    showAlertDialog(context);
  }

  @override
  void initState() {
    super.initState();
    contentList = [];
    _run();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            title: Text(widget.title),
            actions:[
              IconButton(
                  onPressed: () {
                    showAlertDialog(context);
                  },
                  icon: const Icon(Icons.search_outlined),
              )
            ]
        ),
        body: ListView(children: [
          ...items.asMap().keys.map((e) => _itemBuilder(context, e)).toList(),
        ]));
  }

  Widget _itemBuilder(BuildContext context, int index) {
    final item = getItem(index);
    return ItemWidget(
      item: item,
      index: index,
      getGroupRange: () {
        GroupRange data = GroupRange('', 0, 0);
        widget.groupTitle.forEach((key, value) {
          if (value.oldLength <= index && value.newLength >= index) {
            data = GroupRange(key, value.oldLength, value.newLength);
          }
        });
        return data;
      },
      runGroup: (start, end) async {
        for (var i = start; i <= end; i++) {
          await _runTest(i);
          print('\n');
        }
      },
      onTap: (Item item) {
        _runTest(index);
      }
    );
  }

  Item getItem(int index) {
    return items[index];
  }

  @override
  List<Test> get tests => widget.tests;
}

void expect(var testModel, var object) {
  try {
    testModel;
    contentList.add(Text('$testModel'));
  } catch (e) {
    contentList.add(Text(
      '$e',
      style: const TextStyle(color: Colors.red),
    ));
    print(e.toString());
  }
}

void showAlertDialog(BuildContext context) {
  for (int i = 0; i < contentList.length; i++) {
    print(contentList[i].data);
  }
  showDialog<void>(
      context: context,
      barrierDismissible: false,
      builder: (BuildContext context) {
        return AlertDialog(
          content: SingleChildScrollView(
            child: Column(
              children: contentList,
            ),
          ),
          actions: <Widget>[
            MaterialButton(
              child: const Text('确定'),
              onPressed: () {
                Navigator.of(context).pop();
              },
            ),
          ],
        );
      });
}
